/**
 * 桌面版只剩下 tag list 和 creator
 *
 * tag list
 * {
 *   type: 'tag-list,
 *   openViewer: false,
 *   tag: `${tag}`,
 *   mediaType: MEDIA_TYPE,
 *   index: { video: 0, photo: 0 },
 *   fetchers: {
 *     video: useInfinite({ request: API.Search.tagDetail, params: { type: MEDIA_TYPE.VIDEO, keyword: `${tag}` } }),
 *     photo: useInfinite({ request: API.Search.tagDetail, params: { type: MEDIA_TYPE.PHOTO, keyword: `${tag}` } }),
 *   },
 * }
 * 行為:
 *  1. 往下滑觸發繼續更新目前的 media tab 清單
 *  2. 點擊任何 media 會讓桌面版主播放器切換成此請單
 *
 *
 * creator
 * {
 *   type: 'creator',
 *   openViewer: false,
 *   userId: `${userId}`,
 *   userName: `${userName}`, // 不一定會有，setFromCreator 有給的時候才有
 *   mediaType: MEDIA_TYPE,
 *   index: { free: 0, subs: 0, shop: 0 },
 *   fetchers: {
 *     free: useInfinite({ request: API.Media.getPostList, params: { aff: id, visible: 1 } }),
 *     subs: useInfinite({ request: API.Media.getPostList, params: { aff: id, visible: 5 } }),
 *     shop: useInfinite({ request: API.Media.getPostList, params: { aff: id, visible: 10 } }),
 *   },
 * }
 * 行為:
 *  1. 往下滑觸發繼續更新目前的 media tab 清單
 *  2. 點擊任何 media 會讓桌面版主播放器切換成此請單
 *
 *
 * 流程圖如下，後續可無限生長
 *
 *   creator -->  tag-list --> creator -->  tag-list
 */
import { computed, onActivated, onDeactivated, readonly, ref, toRef, watch } from 'vue'
import { useRoute } from 'vue-router'
import { whenever } from '@vueuse/core'
import { useCreatorStore } from '@/store/creator'
import { useDesktopViewerStore } from '@/store/desktop-viewer'
import { useNavStore } from '@/store/nav'
import { contains as containsLocale, locale } from '@/i18n'
import { useInfinite } from '@/compositions/request/infinite'
import { MEDIA_TYPE } from '@/constant'
import {
  CREATOR,
  MAIN_VIEWER,
  MAIN_VIEWER_TYPE,
  MEDIA_TYPE_LABELS,
  TAG_LIST,
  VIEWER_TAB_TYPE,
} from '@/constant/viewer-router'
import { trackEvent } from '@/gtm'
import API from '@/http'

export function useDesktopViewerRouter(viewerName) {
  const DEFAULT_REVISE_STACK_MILLISECOND = 400
  const STACK_REVISING_ACTION_QUEUE = []
  let ORIGIN_PATH = window.location.pathname
  let reviseStackTimeoutId = null
  let reviseStackFn = null

  // page id
  const idMaker = (function* () {
    let id = 0
    while (true) {
      yield id++
    }
  })()

  // 播放器某些操作發生時可能不在自己的nav
  // 提供給外部呼叫把他導回去
  const { toHome, toSearch } = useNavStore()
  const backToSelfNav = (() => {
    switch (useRoute().name) {
      case 'home':
      case 'lang-home':
        return () => toHome({ noRefresh: true })
      case 'search':
      case 'lang-search':
        return () => toSearch({ noRefresh: true })
      default:
        return () => {
          console.warn('Some thing wrong for backToSelfNav...')
        }
    }
  })()

  const { setViewer: setDesktopViewer, setViewerRouter } = useDesktopViewerStore()
  const { getMedia } = useCreatorStore()

  const mainViewer = ref({
    type: MAIN_VIEWER,
    mediaType: MEDIA_TYPE.FORYOU,
    index: { foryou: 0 },
    fetchers: { foryou: null },
  })

  const stack = ref([])
  const currIndex = ref(0)

  // 語言切換時要同步路徑
  watch(locale, (lang) => {
    const pathArr = ORIGIN_PATH.split('/')
    if (containsLocale(pathArr[1])) {
      pathArr[1] = lang
      ORIGIN_PATH = pathArr.join('/')
    }
  })

  // 某些場景會是整個播放器都在畫面之外的，必須把此改為 false 讓某些行為可以判別停止，例如視頻暫停播放
  const active = ref(true)
  let activeTemp
  onActivated(() => {
    if (typeof activeTemp === 'boolean') {
      active.value = activeTemp
    } else {
      active.value = true
    }
  })
  onDeactivated(() => {
    activeTemp = active.value
    active.value = false
  })

  // 正在從目前頁面返回第一頁
  const isBackingToFirst = ref(false)

  // 供外部用來關閉播放器 transition 效果
  // 例如正在從目前貼文前往指定索引貼文時，可能橫跨太多則，此時跑動畫會有點怪
  const disableTransition = ref(false)

  const mainViewerType = ref(MAIN_VIEWER_TYPE.FORYOU)

  const currTabType = ref(VIEWER_TAB_TYPE.NONE)
  watch(
    stack,
    () => {
      const openPage = getOpendPage()
      switch (openPage.type) {
        case MAIN_VIEWER:
          if (mainViewerType.value === MAIN_VIEWER_TYPE.VIDEO_PHOTO) {
            currTabType.value = VIEWER_TAB_TYPE.VIDEO_PHOTO
          } else {
            currTabType.value = VIEWER_TAB_TYPE.NONE
          }
          break
        case TAG_LIST:
          currTabType.value = VIEWER_TAB_TYPE.VIDEO_PHOTO
          break
        case CREATOR:
          currTabType.value = VIEWER_TAB_TYPE.CREATOR
          break
        default:
          currTabType.value = VIEWER_TAB_TYPE.NONE
          break
      }
    },
    { deep: true },
  )

  const noData = computed(() => {
    try {
      const page = getPage(currIndex.value)
      let targetPage
      if (page.openViewer) {
        targetPage = page
      } else {
        if (currIndex.value === 0) {
          targetPage = mainViewer.value
        } else {
          targetPage = getPage(currIndex.value - 1)
        }
      }
      if (targetPage) {
        return targetPage.fetchers[MEDIA_TYPE_LABELS[targetPage.mediaType]]?.dataList.length === 0
      }
    } catch (e) {
      return true
    }
    return false
  })

  const isCreatorPage = computed(() => {
    const page = stack.value[currIndex.value]
    if (!page) return false

    const isOpen = page.openViewer
    return CREATOR === page.type && isOpen
  })

  let fromWhichPage

  function setMainViewer(fetchers) {
    const { foryou, video, photo } = fetchers
    const isForyou = !!foryou
    const isVideoPhoto = !!(!foryou && video && photo)
    const isOnlyVideo = !!(!foryou && video && !photo)
    const isOnlyPhoto = !!(!foryou && !video && photo)

    if (isForyou) {
      mainViewer.value.mediaType = MEDIA_TYPE.FORYOU
      mainViewer.value.index = { foryou: 0 }
      mainViewer.value.fetchers = { foryou }
      mainViewerType.value = MAIN_VIEWER_TYPE.FORYOU
      foryou.init()
    } else if (isVideoPhoto) {
      if (![MEDIA_TYPE.VIDEO, MEDIA_TYPE.PHOTO].includes(mainViewer.value.mediaType)) {
        mainViewer.value.mediaType = MEDIA_TYPE.VIDEO
      }
      mainViewer.value.index = { video: 0, photo: 0 }
      mainViewer.value.fetchers = { video, photo }
      mainViewerType.value = MAIN_VIEWER_TYPE.VIDEO_PHOTO
      video.init()
      photo.init()
    } else if (isOnlyVideo) {
      mainViewer.value.mediaType = MEDIA_TYPE.VIDEO
      mainViewer.value.index = { video: 0 }
      mainViewer.value.fetchers = { video }
      mainViewerType.value = MAIN_VIEWER_TYPE.NONE
      video.init()
    } else if (isOnlyPhoto) {
      mainViewer.value.mediaType = MEDIA_TYPE.PHOTO
      mainViewer.value.index = { photo: 0 }
      mainViewer.value.fetchers = { photo }
      mainViewerType.value = MAIN_VIEWER_TYPE.NONE
      photo.init()
    } else {
      throw new Error(
        'MainViewer is accept only three situation fetcher:\n1. Only one "foryou"\n2. Choose one "video" or "photo"\n3. Both "video" and "photo"',
      )
    }
  }

  function setFromMainViewer(fetchers) {
    reset(MAIN_VIEWER, {}, fetchers)
    setViewerRouter(viewerRouter)
  }

  function setFromCreator(userId, userName) {
    reset(CREATOR, { userId, userName })
    if (userId !== null || userId !== undefined) {
      setViewerRouter(viewerRouter)
    }
  }

  function setFromTagList(tag, { interest, catename } = {}) {
    reset(TAG_LIST, { tag, ...(interest && { interest }), ...(catename && { catename }) })
    if (tag !== null || tag !== undefined) {
      setViewerRouter(viewerRouter)
    }
  }

  function reset(from, { userId, userName, tag, interest, catename }, mainViewerFetchers) {
    // reviseStack 尚未被執行，將此執行放進 action queue 當中
    if (reviseStackTimeoutId !== null) {
      STACK_REVISING_ACTION_QUEUE.push(() => reset(from, { userId, userName, tag }, mainViewerFetchers))
      return
    }

    fromWhichPage = from

    // 清空
    popPage(0)
    currIndex.value = 0

    switch (from) {
      case MAIN_VIEWER:
        setMainViewer(mainViewerFetchers)
        pushPage(CREATOR)
        updateDesktopViewer(mainViewer.value)
        break
      case CREATOR:
        pushPage(CREATOR, { userId, userName })
        getPage(0).openViewer = true
        updateDesktopViewer(getPage(0))
        mainViewerType.value = MAIN_VIEWER_TYPE.VIDEO_PHOTO
        break
      case TAG_LIST:
        pushPage(TAG_LIST, { tag, ...(interest && { interest }), ...(catename && { catename }) })
        getPage(0).openViewer = true
        updateDesktopViewer(getPage(0))
        mainViewerType.value = MAIN_VIEWER_TYPE.VIDEO_PHOTO
        break
      default:
        throw new Error(`${viewerName} - Something went wrong...`)
    }

    changePath()
  }

  function prev() {
    if (reviseStackTimeoutId !== null) return

    const page = getPage(currIndex.value)

    if (currIndex.value === 0) {
      if (page.openViewer && fromWhichPage === MAIN_VIEWER) {
        page.openViewer = false
        updateDesktopViewer(mainViewer.value)
      }
      changePath()
      return
    }

    const prevPage = getPage(currIndex.value - 1)
    if (page.openViewer) {
      page.openViewer = false
      updateDesktopViewer(prevPage)
      changePath()
    } else {
      currIndex.value -= 1
      updateDesktopViewer(prevPage)

      reviseStack(() => {
        popPage()
      })
    }

    disableTransition.value = true
    requestAnimationFrame(() => (disableTransition.value = false))
  }

  /**
   * 返回第一頁
   *
   * 這邊採取一次遞減一頁
   * 讓外部可以跑轉場效果
   * 會與 routeComplete 搭配
   * 持續將 currIndex 減至 0
   *
   * 當 currIndex 減至 0 的時候
   * 後續頁面都會被清掉只剩下第一頁
   */
  function backToFirst() {
    if (reviseStackTimeoutId !== null) return

    if (currIndex.value === 0) return

    isBackingToFirst.value = true

    currIndex.value -= 1

    if (currIndex.value > 0) {
      reviseStack(() => {})
    } else {
      isBackingToFirst.value = false

      reviseStack(() => {
        popPage(1)
        switch (fromWhichPage) {
          case MAIN_VIEWER:
            getPage(0).openViewer = false
            updateDesktopViewer(mainViewer.value)
            break
          case CREATOR:
            getPage(0).openViewer = true
            updateDesktopViewer(getPage(0))
            break
          case TAG_LIST:
            getPage(0).openViewer = true
            updateDesktopViewer(getPage(0))
            break
        }
      })
    }
  }

  function routeComplete() {
    if (reviseStackTimeoutId === null) {
      return
    }

    clearTimeout(reviseStackTimeoutId)
    reviseStackTimeoutId = null

    if (reviseStackFn === null) {
      return
    }

    reviseStackFn()
    reviseStackFn = null

    // reviseStackFn 執行完成後將緩存中的事件執行完
    STACK_REVISING_ACTION_QUEUE.forEach((action) => action())
    STACK_REVISING_ACTION_QUEUE.length = 0

    if (isBackingToFirst.value) {
      backToFirst()
    }
  }

  function nextToTagList(tag) {
    if (reviseStackTimeoutId !== null) return

    const currPage = getPage(currIndex.value)
    if (TAG_LIST === currPage.type) {
      if (currPage.tag !== tag) {
        popPage()
        pushPage(TAG_LIST, { tag })
        const newPage = getPage(currIndex.value)
        requestAnimationFrame(() => {
          newPage.openViewer = true
          updateDesktopViewer(newPage)
          changePath()
        })

        // the 埋
        triggerTrackToTagList(getPage(currIndex.value - 1), tag)
      }
      return
    }

    if (CREATOR === currPage.type) {
      currPage.openViewer = true
      updateDesktopViewer(currPage)
    }

    // 若點選的 tag 跟目前 tag 相同直接拉回前一頁
    if (currIndex.value > 0) {
      const prevPage = getPage(currIndex.value - 1)
      if (prevPage.tag === tag) {
        prev()
        return
      }
    }

    pushPage(TAG_LIST, { tag })
    requestAnimationFrame(() => (currIndex.value += 1))

    // the 埋
    triggerTrackToTagList(currPage, tag)
  }

  function nextToIndexedViewer(index = 0) {
    if (reviseStackTimeoutId !== null) return

    const page = getPage(currIndex.value)
    page.openViewer = true
    page.index[MEDIA_TYPE_LABELS[page.mediaType]] = index

    if (TAG_LIST === page.type) {
      pushPage(CREATOR)
      requestAnimationFrame(() => {
        currIndex.value += 1
        updateDesktopViewer(page)
      })

      // the 埋
      triggerTrackToViewer(page)
    }

    if (CREATOR === page.type) {
      updateDesktopViewer(page)
      changePath()

      // the 埋
      triggerTrackToViewer(page)
    }

    disableTransition.value = true
    requestAnimationFrame(() => (disableTransition.value = false))
  }

  function toIndexedMainViewer(index) {
    if (reviseStackTimeoutId !== null) return

    if (currIndex.value !== 0 || MAIN_VIEWER !== fromWhichPage) {
      throw Error('Something went wrong...')
    }

    mainViewer.value.index[MEDIA_TYPE_LABELS[mainViewer.value.mediaType]] = index

    disableTransition.value = true
    requestAnimationFrame(() => (disableTransition.value = false))
  }

  function getMediaType(index) {
    const page = getPage(index)

    if ([TAG_LIST, CREATOR].includes(page.type)) {
      const mediaTypeRef = toRef(page, 'mediaType')

      if (!page.mediaWatcherStopers) {
        page.mediaWatcherStopers = []
      }

      page.mediaWatcherStopers.push(
        watch(mediaTypeRef, (v) => page.fetchers[MEDIA_TYPE_LABELS[v]]?.init(), { immediate: true }),
      )
      return mediaTypeRef
    }

    throw new Error(`${viewerName} - It's imposible to reach here...`)
  }

  function getViewerIndex(index) {
    const page = getPage(index)

    if ([TAG_LIST, CREATOR].includes(page.type)) {
      return page.index
    }

    throw new Error(
      `${viewerName} - Only PAGE.MAIN_VIEWER, PAGE.TAG_VIEWER and PAGE.CREATOR_VIEWER can get viewer index.`,
    )
  }

  function getVideoFetcher(index) {
    return getMediaFetcher(index, 'video')
  }

  function getPhotoFetcher(index) {
    return getMediaFetcher(index, 'photo')
  }

  function getFreeFetcher(index) {
    return getMediaFetcher(index, 'free')
  }

  function getSubsFetcher(index) {
    return getMediaFetcher(index, 'subs')
  }

  function getShopFetcher(index) {
    return getMediaFetcher(index, 'shop')
  }

  function getMediaFetcher(index, type) {
    const page = getPage(index)

    if ([TAG_LIST, CREATOR].includes(page.type)) {
      return page.fetchers[type]
    }

    throw new Error(`${viewerName} - It's imposible to reach here...`)
  }

  function updateCreator(id) {
    // 正在倒退回第一頁，不需要理會中間頁面的更新
    if (isBackingToFirst.value) return

    // reviseStack 尚未被執行，將此執行放進 action queue 當中
    if (reviseStackTimeoutId !== null) {
      STACK_REVISING_ACTION_QUEUE.push(() => updateCreator(id))
      return
    }

    const page = getPage(currIndex.value)

    if (page.type !== CREATOR || page.userId === id) {
      return
    }

    page.userId = id

    const media = getMedia(id)
    page.index = { free: 0, subs: 0, shop: 0 }
    page.fetchers = { free: media.free, subs: media.subs, shop: media.shop }
  }

  function reviseStack(fn) {
    reviseStackFn = fn

    // 預設時間內沒被執行就直接執行
    reviseStackTimeoutId = setTimeout(routeComplete, DEFAULT_REVISE_STACK_MILLISECOND)
  }

  function pushPage(type, data) {
    const page = {
      id: idMaker.next().value,
      type,
      openViewer: false,
      ...data,
    }

    if (TAG_LIST === type) {
      page.mediaType = MEDIA_TYPE.VIDEO
      page.index = { video: 0, photo: 0 }
      page.fetchers = {
        video: useInfinite({
          request: API.Search.tagDetail,
          params: { type: MEDIA_TYPE.VIDEO, keyword: page.tag, ...(page.interest && { interest: page.interest }) },
        }),
        photo: useInfinite({
          request: API.Search.tagDetail,
          params: { type: MEDIA_TYPE.PHOTO, keyword: page.tag, ...(page.interest && { interest: page.interest }) },
        }),
      }
    }

    if (CREATOR === type) {
      page.mediaType = MEDIA_TYPE.SUBS
      page.index = { free: 0, subs: 0, shop: 0 }
      if (page.userId) {
        const media = getMedia(page.userId)
        page.fetchers = { free: media.free, subs: media.subs, shop: media.shop }
      }
    }

    stack.value.push(page)
  }

  function popPage(restCount) {
    // pop 至剩餘頁數
    if (typeof restCount === 'number') {
      if (stack.value.length > restCount) {
        const pages = stack.value.splice(restCount)
        pages.forEach((page) => cleanPageEffect(page))
      }
    }

    // pop 最後一頁
    else {
      if (stack.value.length > 0) {
        const page = stack.value.pop()
        cleanPageEffect(page)
      }
    }
  }

  function cleanPageEffect(page) {
    page.mediaWatcherStopers?.forEach((stop) => stop())
  }

  function getPage(index) {
    const lastIndex = stack.value.length - 1
    if (index > lastIndex) {
      throw new Error(`${viewerName} out of index "${index}" for last is "${stack.value.length - 1}"`)
    }
    return stack.value[index]
  }

  function getOpendPage() {
    let openedPage
    for (let i = stack.value.length - 1; i >= 0; i--) {
      const page = stack.value[i]
      if (page.openViewer) {
        openedPage = page
        break
      }
    }
    return openedPage || mainViewer.value
  }

  function updateDesktopViewer(page) {
    if (MAIN_VIEWER === page.type) {
      if (mainViewerType.value === MAIN_VIEWER_TYPE.FORYOU) {
        setDesktopViewer({
          mediaType: toRef(page, 'mediaType'),
          foryouIndex: toRef(page.index, 'foryou'),
          foryouFetcher: page.fetchers.foryou,
        })
      } else {
        setDesktopViewer({
          mediaType: toRef(page, 'mediaType'),
          videoIndex: page.fetchers.video ? toRef(page.index, 'video') : undefined,
          photoIndex: page.fetchers.photo ? toRef(page.index, 'photo') : undefined,
          videoFetcher: page.fetchers.video,
          photoFetcher: page.fetchers.photo,
        })
      }
    } else if (CREATOR === page.type) {
      setDesktopViewer({
        mediaType: toRef(page, 'mediaType'),
        freeIndex: toRef(page.index, 'free'),
        subsIndex: toRef(page.index, 'subs'),
        shopIndex: toRef(page.index, 'shop'),
        freeFetcher: page.fetchers.free,
        subsFetcher: page.fetchers.subs,
        shopFetcher: page.fetchers.shop,
      })
    } else {
      setDesktopViewer({
        mediaType: toRef(page, 'mediaType'),
        videoIndex: toRef(page.index, 'video'),
        photoIndex: toRef(page.index, 'photo'),
        videoFetcher: page.fetchers.video,
        photoFetcher: page.fetchers.photo,
      })
    }
  }

  function changePath() {
    if (stack.value.length === 0) {
      return
    }

    const page = getPage(currIndex.value)
    switch (page?.type) {
      case TAG_LIST:
        if (page.catename) window.history.replaceState({}, '', `/tag/${page.catename}/${page.tag}`)
        else window.history.replaceState({}, '', `/tag/${page.tag}`)
        break
      case CREATOR:
        if (page.openViewer) {
          if (page.userName) {
            window.history.replaceState({}, '', `/${page.userName}`)
          } else {
            if (currIndex.value === 0 && fromWhichPage === CREATOR) {
              revertPath()
            } else {
              const label = MEDIA_TYPE_LABELS[page.mediaType]
              const userName = page.fetchers[label].dataList[page.index[label]]?.author?.username
              if (userName) {
                window.history.replaceState({}, '', `/${userName}`)
              }
            }
          }
        } else {
          if (currIndex.value === 0) {
            revertPath()
          } else {
            window.history.replaceState({}, '', `/tag/${getPage(currIndex.value - 1).tag}`)
          }
        }
        break
      default:
        revertPath()
        break
    }
  }

  function triggerTrackToTagList(page, tag) {
    if (page.mediaType === MEDIA_TYPE.VIDEO) {
      const feed = page.fetchers.video.dataList[page.index.video]
      trackEvent({ key: 10, meta: { id: feed.id, tag } })
    }
    if (page.mediaType === MEDIA_TYPE.PHOTO) {
      const feed = page.fetchers.photo.dataList[page.index.photo]
      trackEvent({ key: 10, meta: { id: feed.id, tag } })
    }
  }

  function triggerTrackToViewer(page) {
    const [key, feed] = (() => {
      if (page.mediaType === MEDIA_TYPE.VIDEO) {
        return [16, page.fetchers.video.dataList[page.index.video]]
      }
      if (page.mediaType === MEDIA_TYPE.PHOTO) {
        return [17, page.fetchers.photo.dataList[page.index.photo]]
      }
      if (page.mediaType === MEDIA_TYPE.FREE) {
        return [70, page.fetchers.free.dataList[page.index.free]]
      }
      if (page.mediaType === MEDIA_TYPE.SUBS) {
        return [71, page.fetchers.subs.dataList[page.index.subs]]
      }
      if (page.mediaType === MEDIA_TYPE.SHOP) {
        return [72, page.fetchers.shop.dataList[page.index.shop]]
      }
    })()
    if (key && feed) {
      trackEvent({ key, meta: { id: feed.id, aff: feed.author.aff } })
    }
  }

  // 通知 DesktopViewer 換回來
  onActivated(() => {
    if (!fromWhichPage) return

    const openedPage = getOpendPage()
    updateDesktopViewer(openedPage)
  })

  // 重設 desktopViewerStore 中的 viewerRouter
  onActivated(() => {
    let willSet = false
    switch (fromWhichPage) {
      case CREATOR: {
        const { userId } = getPage(0)
        willSet = userId !== null && userId !== undefined
        break
      }
      case TAG_LIST: {
        const { tag } = getPage(0)
        willSet = tag !== null && tag !== undefined
        break
      }
      case MAIN_VIEWER:
      default:
        willSet = true
        break
    }
    if (willSet) {
      setViewerRouter(viewerRouter)
    }
  })

  watch(currIndex, () => changePath())
  whenever(active, () => changePath())
  function setOriginPath(originPath) {
    ORIGIN_PATH = originPath
  }
  function revertPath() {
    setTimeout(() => window.history.replaceState({}, '', ORIGIN_PATH), 0)
  }

  const viewerRouter = {
    pages: readonly(stack),
    currIndex: readonly(currIndex),
    active,
    isBackingToFirst: readonly(isBackingToFirst),
    disableTransition: readonly(disableTransition),
    mainViewerType: readonly(mainViewerType),
    currTabType: readonly(currTabType),
    noData,

    // 桌面版 tag 是否要顯示 shop 判斷用
    isCreatorPage,

    setFromMainViewer,
    setFromCreator,
    setFromTagList,
    setOriginPath,
    revertPath,

    // route
    prev,
    backToFirst,
    nextToTagList,
    nextToIndexedViewer,
    toIndexedMainViewer,

    // get
    getMediaType,
    getViewerIndex,
    getVideoFetcher,
    getPhotoFetcher,
    getFreeFetcher,
    getSubsFetcher,
    getShopFetcher,

    updateCreator,

    routeComplete,

    // 某些情境下點擊播放器時，右邊的畫面可能不在自己的 nav，提供給外部調用跳回自己 nav
    backToSelfNav,
  }

  return viewerRouter
}
